import java.util.*;
import java.awt.Point;

public class BattleshipGame {
	//This class runs the game, controlling the computer AI, handling the game rules and printing output to the screen
	
	private char[][] board;
	private char [][] playerBoard;
	private int numberOfMoves; //keeps track of the number of moves
	private int numberOfCompHits;//
	private int sizeX, sizeY; //board (X,Y) sizes
	private int bHit, dHit, cHit, tHit; //keep track of hits against the ships
	private String playerName;
	private boolean lastHitSuccess = false; //Sets to true if the CPU successfully hit a ship last round
	private Point lastPntSuccess; //Last successful point hit by the CPU
	private boolean[] triedDirections = new boolean[4]; //Records the directions tried by the game AI.
	
	private Point[] destroyerPoints = new Point[4]; //store the destroyers point locations in an array
	private Point[] battleshipPoints = new Point[5];
	private Point[] cruiserPoints = new Point[3];
	private Point[] tugboatPoints = new Point[2];
	
	public final static int MOVE_MISS    = 0;
	public final static int MOVE_HIT     = 1;
	public final static int MOVE_INVALID = 2;
	
	public final static String[] ResultMessages = {" that is a miss."," that is a hit!", ", that is an invalid move."};	

	// Constructor that sets the game up for play
	public BattleshipGame() {
		//Board should handle up to 10x10 board size no problem (requirement 1.2)
		sizeX = 10;
		sizeY = 10;
		bHit = 0; 
		dHit = 0; 
		cHit = 0; 
		tHit = 0;
		numberOfCompHits = 0;
		numberOfMoves = 0;
		board = new char[sizeY][sizeX];
		playerBoard = new char[sizeY][sizeX];
		
		initializeBoard(true);
		placeShip(5, rndBool(), 'B');  // battleship
		placeShip(4, rndBool(), 'D'); // destroyer
		placeShip(3, rndBool(), 'C');  // cruiser
		placeShip(2, rndBool(), 'T'); // tug
	}
	
	// Initializes the board for game use
	public void initializeBoard(boolean compInit) {
		// This sets all the squares to be waves
		for (int i = 0; i < board.length; i++) {
			for (int j = 0; j < board[i].length; j++) {
				if (compInit)
					board[i][j] = '~'; //Computer board
				playerBoard[i][j] = '~'; //Player board
			}
		}
	}
	
	// Initializes the user's ships on the board
	public boolean userInitialization(String name, String battleship, String destroyer, String cruiser, String tug){
		//Call this function to initialize the user's board
		playerName = name;
		if (battleship.equals("*")){
			placeUserShip(5,rndBool(), 'B',-1,-1);
			placeUserShip(4,rndBool(),'D',-1,-1);
			placeUserShip(3,rndBool(),'C',-1,-1);
			placeUserShip(2,rndBool(),'T',-1,-1);
			return true;
		}else{
			int shipX, shipY; //Current ship's integers
			boolean shipDir; //Current ship's orientation
			
			//-[BATTLESHIP]-
			StringTokenizer pieces = new StringTokenizer(battleship, ",");
			shipX = Integer.parseInt(pieces.nextToken());
			shipY = Integer.parseInt(pieces.nextToken());
			if (pieces.nextToken().equals("V")) {
				shipDir = true;
			}else {
				shipDir = false;
			}
			boolean bshipplace = placeUserShip(5,shipDir, 'B',shipX,shipY); //Place the Battleship
			
			//-[DESTROYER]-
			pieces = new StringTokenizer(destroyer, ",");
			shipX = Integer.parseInt(pieces.nextToken());
			shipY = Integer.parseInt(pieces.nextToken());
			if (pieces.nextToken().equals("V")) {
				shipDir = true;
			}else {
				shipDir = false;
			}
			boolean dshipplace = placeUserShip(4,shipDir, 'D',shipX,shipY); //Place the Destroyer
			
			//-[CRUISER]-
			pieces = new StringTokenizer(cruiser, ",");
			shipX = Integer.parseInt(pieces.nextToken());
			shipY = Integer.parseInt(pieces.nextToken());
			if (pieces.nextToken().equals("V")) {
				shipDir = true;
			}else {
				shipDir = false;
			}
			boolean cshipplace = placeUserShip(3,shipDir, 'C',shipX,shipY); //Place the Cruiser
			
			//-[TUGBOAT]-
			pieces = new StringTokenizer(tug, ",");
			shipX = Integer.parseInt(pieces.nextToken());
			shipY = Integer.parseInt(pieces.nextToken());
			if (pieces.nextToken().equals("V")) {
				shipDir = true;
			}else {
				shipDir = false;
			}
			boolean tshipplace = placeUserShip(2,shipDir, 'T',shipX,shipY); //Place the Tugboat

			return (bshipplace && dshipplace && cshipplace && tshipplace);
		}
	
	}
	
	// Returns a random boolean for ship orientation placement
	public boolean rndBool(){
		Random rand = new Random();
		return rand.nextBoolean();
	}
	
	public boolean placeUserShip(int shipSize, boolean xOriented, char symbol, int shipX, int shipY) {
		// Places a ship of size shipSize, 
		// either along the x or y axis (no diagonal ships)FOR THE COMPUTER
		int starty = shipX, startx = shipY;
		int x, y;

		if (xOriented) {
			x = 1;
			y = 0;
		} else {
			x = 0;
			y = 1;
		}

		boolean goodSpot = false;
		
		if(startx == -1 && starty == -1){
			while (!goodSpot) {
				// Choose a random spot to put the end of the ship
				startx = (int) (Math.random() * (playerBoard.length - shipSize*x));	
				starty = (int) (Math.random() * (playerBoard[0].length - shipSize*y));
				goodSpot = true;
				
				// Verify that nothing else is already placed in any of the squares you need
				for (int i=0; i < shipSize; ++i) {
					if (playerBoard[startx+(i*x)][starty+(i*y)] != '~') {
						goodSpot = false;
					}	
				}
			}
		}	
		else{
		// Verify that nothing else is already placed in any of the squares you need
			for (int i=0; i < shipSize; ++i) {
				try{
					if (playerBoard[startx+(i*x)][starty+(i*y)] != '~') {
						System.out.println("Sorry, one of your ships are overlapping.");
						initializeBoard(false);
						return false;
					}
				}catch (ArrayIndexOutOfBoundsException e){
						//Handle the array out of bounds
						System.out.println("Sorry, one of your ships is out of bounds.");
						initializeBoard(false);
						return false;
				}	
			}
		}		
		for (int i=0; i < shipSize; ++i) {
			playerBoard[startx+(i*x)][starty+(i*y)] = symbol;	
		}		
		return true;
	}
	
	// Places the Computer Player's ships on the board
	public void placeShip(int shipSize, boolean xOriented, char symbol) {
		// Places a ship of size shipSize, 
		// either along the x or y axis (no diagonal ships)FOR THE COMPUTER
		int startx = 0, starty = 0;
		int x, y;

		if (xOriented) {
			x = 1;
			y = 0;
		} else {
			x = 0;
			y = 1;
		}

		boolean goodSpot = false;
		while (!goodSpot){
			// Choose a random spot to put the end of the ship
			startx = (int) (Math.random() * (board.length - shipSize*x));	
			starty = (int) (Math.random() * (board[0].length - shipSize*y));
			goodSpot = true;
			
			// Verify that nothing else is already placed in any of the squares you need
			for (int i=0; i < shipSize; ++i) {
				if (board[startx+(i*x)][starty+(i*y)] != '~') {
					goodSpot = false;
				}	
			}
		}	
		for (int i=0; i < shipSize; ++i) {
			board[startx+(i*x)][starty+(i*y)] = symbol;
			//Add items to the xPoints arrays depending on the object
			if (symbol == 'D') 
				destroyerPoints[i] = new Point(startx+(i*x),starty+(i*y));
			if (symbol == 'B') 
				battleshipPoints[i] = new Point(startx+(i*x),starty+(i*y));	
			if (symbol == 'C') 
				cruiserPoints[i] = new Point(startx+(i*x),starty+(i*y));	
			if (symbol == 'T') 
				tugboatPoints[i] = new Point(startx+(i*x),starty+(i*y));		
		}
	}

	// Outputs the updated gameboard to the screen
	public void printGameBoard(boolean showShipPositions) {
		//This method displays the game board
		//if showShipPositions is true the positions of
		//the ships are also shown
		String output;
		
		//Print out table titles
		output = "  Challenger";
		for(int i = 0; i < board[0].length; i++){
			output += " "; 
		}
		output += "    Program";
		System.out.println(output);
		
		//Output First table 
		output = "  ";
		for (int i = 0; i < board[0].length; i++) {
			output = output +  i + " ";
		}	
		System.out.print(output);
		
		//Output Second table
		output = "    ";
		for (int i = 0; i < playerBoard[0].length; i++){
			output += i + " ";
		}
		System.out.println(output);

		//Render board (show hits)
		for (int i = 0; i < board.length; i++) {
			output = "" + i + " ";			
			//Challenger's Board (visible)
			for (int j = 0; j < playerBoard[i].length; j++) {
					//Show positions of all items
				    output += playerBoard[i][j] + " ";	
			}	
			output += "  " + i + " ";
			
			//Program's Board (not visible)
			for (int j = 0; j < board[i].length; j++) {
				if (board[i][j] == '.' || board[i][j] == 'X') {
					char renderChar = board[i][j];
					if (bHit > 5){
						for(int k = 0; k < battleshipPoints.length; k++)
						{
							if (i == battleshipPoints[k].getX() && j == battleshipPoints[k].getY())
								renderChar = 'B';
						}
					}
					if (cHit > 3){
						for(int k = 0; k < cruiserPoints.length; k++)
						{
							if (i == cruiserPoints[k].getX() && j == cruiserPoints[k].getY())
								renderChar = 'C';
						}
					}
					if (tHit > 2){
						for(int k = 0; k < tugboatPoints.length; k++)
						{
							if (i == tugboatPoints[k].getX() && j == tugboatPoints[k].getY())
								renderChar = 'T';
						}
					}
					if (dHit > 4){
						for(int k = 0; k < destroyerPoints.length; k++)
						{
							if (i == destroyerPoints[k].getX() && j == destroyerPoints[k].getY())
								renderChar = 'D';
						}
					}
					output += renderChar + " ";
				}
				else if(showShipPositions){
					//show the positions of the ships
				    output += board[i][j] + " ";	
				}
				else{
					output += '~' + " ";
				}
			}
			output += "\n";
			System.out.print(output);
		}
	}
	
	// Returns whether a move was successful or not in the form of a string
	public String move(int x, int y) {
		numberOfMoves++;
		// Checks if the coordinates are out of bound
		if (x >= board.length || x < 0 || 
			y >= board[0].length || y < 0) {
			return ResultMessages[MOVE_INVALID];
		}
		// Check if the move made was a miss
		if (board[x][y] == '~' || board[x][y] == '.') {
			board[x][y] = '.';
			challengerMove(); //If the move was successful, allow the computer to make a move
			return ResultMessages[MOVE_MISS];
		}
		else {
			// If it isn't invalid and wasn't a miss, then it must be a hit
			if (board[x][y] == 'D') {dHit++;}
			if (board[x][y] == 'C')	{cHit++;}
			if (board[x][y] == 'B')	{bHit++;}
			if (board[x][y] == 'T') {tHit++;}
			board[x][y] = 'X';
			
			if(dHit == 4){ dHit++; return ResultMessages[MOVE_HIT] + " You sank my destroyer!";}
			if(bHit == 5){ bHit++; return ResultMessages[MOVE_HIT] + " You sank my battleship!";}
			if(cHit == 3){ cHit++; return ResultMessages[MOVE_HIT] + " You sank my cruiser!";}
			if(tHit == 2){ tHit++; return ResultMessages[MOVE_HIT] + " You sank my tugboat!";}
			
			challengerMove(); //If the move was successful, allow the computer to make a move
			return ResultMessages[MOVE_HIT];
		}
	}
	
	// Allows the computer to make a move on the board
	public void challengerMove(){
		//lastHitSuccess (bool), lastPntSuccess (Point)
		int moveX, moveY;
		moveX = -1; moveY = -1;
		if(!lastHitSuccess){
			// No last successful hit, randomly make a move
			boolean goodSpot = false;
			while (!goodSpot) {
				// Choose a random spot to put the end of the ship
				moveX = (int)(Math.random() * (playerBoard.length));	
				moveY = (int)(Math.random() * (playerBoard[0].length));
				goodSpot = true;
				
				// Verify that nothing else is already placed in any of the squares you need
					if (playerBoard[moveX][moveY] == '.') {
						goodSpot = false;
					}	
			}	
			if (playerBoard[moveX][moveY] == '~'){
				playerBoard[moveX][moveY] = '.';
			}else if (playerBoard[moveX][moveY] != 'X' && playerBoard[moveX][moveY] != '.') {
				playerBoard[moveX][moveY] = 'X';
				lastHitSuccess = true;
				lastPntSuccess = new Point(moveX,moveY);
				numberOfCompHits++; //Increment the number of computer player hits
			}	
		}else{
			// Assumes that the last hit was successful, tries squares to the left, right, top and bottom of the hit.
			// If it cannot find a hit after testing those 4 spots, it moves back into random mode. If it hits,
			// it resets its internal counter and tries again.
			
			boolean successfulMove = false;
			Random rand = new Random();
			int direction = rand.nextInt(4);
			
			try{
			while(!successfulMove){
			
				//0 - Left, 1 - Up, 2 - Right, 3 - Down
				//TODO: GET THE CASES TO WORK PROPERLY TO INVESTIGATE AROUND THE MATCHED SQUARE
				if(triedDirections[0] && triedDirections[1] && triedDirections[2] && triedDirections[3]){
					//If all the directions are used up, clear them out and return to random
					triedDirections = new boolean[4];
					lastHitSuccess = false;
				}
					while(triedDirections[direction]){
						direction = rand.nextInt(4);
					}
					triedDirections[direction] = true;
					
					switch(direction){
						case 0:
							if (lastPntSuccess.getX() > 0)
								moveX = (int)lastPntSuccess.getX()-1;
							moveY = (int)lastPntSuccess.getY();
				
						case 1:
							moveX = (int)lastPntSuccess.getX();
							if (lastPntSuccess.getY() > 0)
								moveY = (int)lastPntSuccess.getY()-1;
						
						case 2:
							if (lastPntSuccess.getX() < sizeX)
								moveX = (int)lastPntSuccess.getX()+1;
							moveY = (int)lastPntSuccess.getY();
							
						case 3:
							moveX = (int)lastPntSuccess.getX();
							if (lastPntSuccess.getX() < sizeY)
								moveY = (int)lastPntSuccess.getY()+1;
								
						default:
							successfulMove = true;
							lastHitSuccess = false;
				}
				//CHECK IF MOVE HAS ALREADY BEEN PERFORMED, IF SO TRY AGAIN
				if (playerBoard[moveX][moveY] == '~'){
					playerBoard[moveX][moveY] = '.';
					successfulMove = true;
				}else if (playerBoard[moveX][moveY] != 'X' && playerBoard[moveX][moveY] != '.'){
					playerBoard[moveX][moveY] = 'X';
					lastHitSuccess = true;
					lastPntSuccess = new Point(moveX,moveY);
					triedDirections = new boolean[4];
					numberOfCompHits++; //Increment the number of computer player hits
					successfulMove = true;
				}
			}
			}catch (Exception e){
				e.getMessage();
			}
			
		}
	}
	// Determines if the game has been won yet by the CPU or the User. 
	public boolean isWon() {
		// If it has been won, print the appropriate message.
		boolean won = true;
		for (int i = 0; i < board.length; ++i) {
			for (int j = 0; j < board[i].length; ++j) {
				if ((board[i][j] != '~' && board[i][j] != '*' && board[i][j] != '.') || (playerBoard[i][j] != '~' && playerBoard[i][j] != '*' && playerBoard[i][j] != '.')) {
					won = false;
				}
			}
		}
		if (dHit + bHit + cHit + tHit == 18){
			System.out.println("Player wins!");
			won = true;
		}
		if (numberOfCompHits == 15){
			System.out.println("Computer wins!");
			won = true;
		}
		return won;
	}
}
